package org.dreamwork.cai.proxy;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.dreamwork.cai.internal.pojo.JsonInvocationPayload;
import org.dreamwork.cai.internal.pojo.JsonInvocationResult;
import org.dreamwork.cai.srf.*;
import org.dreamwork.gson.GsonHelper;
import org.dreamwork.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;

public class ProxyInvoker {
    private static final Logger logger = LoggerFactory.getLogger (ProxyInvoker.class);

    public static JsonInvocationResult invoke (IServiceResolver resolver, JsonInvocationPayload payload) {
        if (logger.isTraceEnabled ()) {
            logger.trace ("json invocation request: \r\n{}", new GsonBuilder ().setPrettyPrinting ().create ().toJson (payload));
        }

        ServiceRouteTable table = resolver.get (ServiceRouteTable.class);
        ServiceRouteFrame local = table.get (resolver.getLocalNamespace ());
        ServiceRouteItem sri = local.get (payload.name);
        MethodInfo info = sri.get (payload.uuid);

        Gson g = GsonHelper.getGson (false, true);
        JsonInvocationResult jir = null;

        if (info != null) {
            Object bean = resolver.get (sri.name);
            Object[] args = new Object[info.parameters.size ()];
            if (!StringUtil.isEmpty (payload.params)) {
                Object[] transformed = g.fromJson (payload.params, Object[].class);

                for (int i = 0; i < info.parameters.size (); i++) {
                    ParameterInfo pi = info.parameters.get (i);
                    if (transformed[i] != null) {
                        Class<?> pt = transformed[i].getClass ();
                        if (pi.javaType.isAssignableFrom (pt)) {
                            args[i] = transformed[i];
                        } else {
                            String temp = g.toJson (transformed[i]);
                            args[i] = g.fromJson (temp, pi.javaType);
                        }
                    }
                }
            }

            jir = invoke (bean, info, g, args);
        }

        if (jir == null) {
            logger.warn ("can't find requested service: {}/{}/{}", payload.namespace, payload.name, payload.uuid);
            jir = new JsonInvocationResult ();
            jir.code = 404;
            jir.error = "Service Not Available";
        }
        return jir;
    }

    private static Object invoke (Object o, Method m, Object... args) throws Exception {
        if (o == null || m == null) {
            throw new NullPointerException ();
        }

        Class<?> type = o.getClass ();
        Class<?>[] ps = m.getParameterTypes ();
        Method method = type.getDeclaredMethod (m.getName (), ps);
        return method.invoke (o, args);
    }

    private static JsonInvocationResult invoke (Object bean, MethodInfo info, Gson g, Object... args) {
        JsonInvocationResult jir = new JsonInvocationResult ();
        try {
            Object o = invoke (bean, info.method, args);
            jir.result = g.toJson (o);
            jir.code = 0;
        } catch (Exception ex) {
            logger.warn (ex.getMessage (), ex);

            jir.code = -1;
            jir.error = ex.getMessage ();
        }
        return jir;
    }
}
